package controler.tools;

import java.io.*;

import java.util.Arrays;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import model.data.*;

/** 
 * @file Loader.java  
 * 
 **/

/**
 * @class Loader
 * @brief Wczytywanie plikow
 * 
 *        Klasa pomocnicza, umozliwiająca pobieranie danych pomiarowych z plikow
 *        z wyroznieniem dwoch zbiorow - testowego i uczacego.
 * 
 * @param list
 *            Lista obiektow klasy Measurement do ktorej wrzucane są nowo-powstale obiekty pomiarowe.
 *            
 * @param tmpList
 *            Tymczasowa, pomocnicza kopia listy list. Jest wykorzystywana w celu skrocenia czasu
 *            wyszukiwania konkretnych pomiarow podczas aktualizacji danych.
 *            
 * @param dataSource
 *            Zrodlo danych, efekt koncowy dzialania tej klasy.
 *            
 */

public class Loader {

	private LinkedList<Measurement> list = new LinkedList<Measurement>();
	private LinkedList<Measurement> tmpList = new LinkedList<Measurement>();
	private DataSource dataSource;

	/**
	 * @brief Metoda wczytujaca dane z pliku.
	 * 
	 *        Metoda wczytuje sciezke do pliku lub katalogu, a nastepnie
	 *        przetwarza dane zaleznie od typu pliku, inaczej dla stron html,
	 *        inaczej dla plikow tekstowych z danymi odzielonymi przecinkami
	 */

	public void load(String path) {
		File file = new File(path);

		if (file.isDirectory()) {
			String[] filesList = file.list();
			Arrays.sort(filesList);
			for (int i = 0; i < filesList.length; ++i) {
				// System.out.println(filesList[i]);
				load(file.getAbsolutePath() + "//" + filesList[i]);
			}
		} else {
			if (path.matches(".*\\.txt$")) {
				processFile(file.getAbsolutePath());
			} else if (path.matches(".*[\\d]+$")) {
				processHTMLFile(file.getAbsolutePath());
				System.out.println(file.getAbsolutePath());
			}

		}

		dataSource = new DataSource(list);
	}

	/**
	 * @brief Metoda przetwarzająca dane z pliku tekstowego.
	 * 
	 *        Metoda wczytuje plik tekstowy, nastepne linijka po linijce parsuje
	 *        wszystkie dane, tworzy nowy obiekt typu Measurement i zapisuje dane 
	 *        do niego.
	 *        
	 *    
	 */

	private void processFile(String fileName) {

		try {
			BufferedReader br = new BufferedReader(new FileReader(fileName));
			String line;
			int lineNumber = 0;
			br.readLine();
			while ((line = br.readLine()) != null) {
				++lineNumber;
				String[] fields = line.split("\\;", 0);

				Measurement measurement = createMeasurement(fields, lineNumber);
				list.add(measurement);
				tmpList.add(measurement);

				//System.out.println(measurement.getTime());
				// System.out.println(new Date(measurement.getTime()));

			}
			br.close();

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * @brief Metoda przetwarzająca dane ze strony.
	 * 
	 *        Metoda wczytuje plik z kodem html, za pomoca wyrazen regularnych
	 *        szuka odpowiednich wzorcow takich jak znaczniki z opadami czy
	 *        temperatura a nastepnie uzupelnia o te dane wczesniejszą liste z
	 *        informacjami drogowymi. 
	 *    
	 */
	
	public void processHTMLFile(String fileName) {
		double[][] values = new double[18][12];
		long[] times = new long[18];

		try {
			BufferedReader br = new BufferedReader(new FileReader(fileName));
			String line;
			long dateInMillis = 0l;

			Pattern p = Pattern.compile("^[\\D]+([0-9]+)$");
			Matcher m = p.matcher(fileName);

			if (m.find()) {
				dateInMillis = Long.parseLong(m.group(1)) * 1000l;
			}

			Calendar calendar = Calendar.getInstance();
			calendar.setTimeInMillis(dateInMillis);
			String date = calendar.get(Calendar.YEAR) + ".";
			Integer month = calendar.get(Calendar.MONTH) + 1;
			String monthStr = month.toString();
			Integer day = calendar.get(Calendar.DAY_OF_MONTH);
			String dayStr = day.toString();

			if (monthStr.length() < 2) {
				monthStr = "0" + monthStr;
			}
			date += monthStr + ".";

			if (dayStr.length() < 2) {
				dayStr = "0" + dayStr;
			}
			date += dayStr;

			System.out.println(date);

			while ((line = br.readLine()) != null) {
				if (line.matches("^.*>" + date + "<.*$")) {
					String nextLine = br.readLine();
					Scanner sc = new Scanner(nextLine)
							.useDelimiter("<[\\D|^-]*>");
					int i = 0;
					while (sc.hasNext()) {
						String hour = sc.next();
						String[] time = hour.split("\\:", 2);
						calendar.set(Calendar.HOUR_OF_DAY,
								Integer.parseInt(time[0]));
						calendar.set(Calendar.MINUTE, Integer.parseInt(time[1]));
						times[i] = calendar.getTimeInMillis();
						++i;
					}
				}

				if (line.matches("^.*<b>Temperatura powietrza 2m</b>.*$")) {
					int i = 0;
					String nextLine = br.readLine();
					Scanner sc = new Scanner(nextLine)
							.useDelimiter("<[\\D|^-]*>");
					while (sc.hasNext()) {
						String str = sc.next().trim();
						if (str.matches("[\\d|\\.|-]+")) {
							values[i][0] = Double.parseDouble(str);
							i++;
						}
					}
				}

				if (line.matches("^.*<b>Temperatura odczuwalna</b>.*$")) {
					int i = 0;
					String nextLine = br.readLine();
					Scanner sc = new Scanner(nextLine)
							.useDelimiter("<[\\D|^-]*>");
					while (sc.hasNext()) {
						String str = sc.next().trim();
						if (str.matches("[\\d|\\.|-]+")) {
							values[i][1] = Double.parseDouble(str);
							i++;
						}
					}
				}

				if (line.matches("^.*<b>Temperatura powietrza 20cm</b>.*$")) {
					int i = 0;
					String nextLine = br.readLine();
					Scanner sc = new Scanner(nextLine)
							.useDelimiter("<[\\D|^-]*>");
					while (sc.hasNext()) {
						String str = sc.next().trim();
						if (str.matches("[\\d|\\.|-]+")) {
							values[i][2] = Double.parseDouble(str);
							i++;
						}
					}
				}

				if (line.matches("^.*<b>Temperatura nawierzchni 0cm</b>.*$")) {
					int i = 0;
					String nextLine = br.readLine();
					Scanner sc = new Scanner(nextLine)
							.useDelimiter("<[\\D|^-]*>");
					while (sc.hasNext()) {
						String str = sc.next().trim();
						if (str.matches("[\\d|\\.|-]+")) {
							values[i][3] = Double.parseDouble(str);
							i++;
						}
					}
				}

				if (line.matches("^.*<b>Temperatura nawierzchni .minus;5cm</b>.*$")) {
					int i = 0;
					String nextLine = br.readLine();
					Scanner sc = new Scanner(nextLine)
							.useDelimiter("<[\\D|^-]*>");
					while (sc.hasNext()) {
						String str = sc.next().trim();
						if (str.matches("[\\d|\\.|-]+")) {
							values[i][4] = Double.parseDouble(str);
							i++;
						}
					}
				}

				if (line.matches("^.*<b>Temperatura punktu rosy</b>.*$")) {
					int i = 0;
					String nextLine = br.readLine();
					Scanner sc = new Scanner(nextLine)
							.useDelimiter("<[\\D|^-]*>");
					while (sc.hasNext()) {
						String str = sc.next().trim();
						if (str.matches("[\\d|\\.|-]+")) {
							values[i][5] = Double.parseDouble(str);
							i++;
						}
					}
				}

				if (line.matches("^.*<b>Wilgotno.. powietrza 2m</b>.*$")) {
					int i = 0;
					String nextLine = br.readLine();
					Scanner sc = new Scanner(nextLine)
							.useDelimiter("<[\\D|^-]*>");
					while (sc.hasNext()) {
						String str = sc.next().trim();
						if (str.matches("[\\d|\\.|-]+")) {
							values[i][6] = Double.parseDouble(str);
							i++;
						}
					}
				}

				if (line.matches("^.*<b>Intensywno.. opadu</b>.*$")) {
					int i = 0;
					String nextLine = br.readLine();
					Scanner sc = new Scanner(nextLine).useDelimiter("<[\\D|^-]*>");
					while (sc.hasNext()) {
						String str = sc.next().trim();
						if (str.matches("[\\d|\\.|-]+")) {
							values[i][7] = Double.parseDouble(str);
							i++;
						}
					}
				}

				if (line.matches("^.*<b>Przewodno.. nawierzchni</b>.*$")) {
					int i = 0;
					String nextLine = br.readLine();
					Scanner sc = new Scanner(nextLine)
							.useDelimiter("<[\\D|^-]*>");
					while (sc.hasNext()) {
						String str = sc.next().trim();
						if (str.matches("[\\d|\\.|-]+")) {
							values[i][8] = Double.parseDouble(str);
							i++;
						}
					}
				}

				if (line.matches("^.*<b>Pr.dko.. wiatru</b>.*$")) {
					int i = 0;
					String nextLine = br.readLine();
					Scanner sc = new Scanner(nextLine).useDelimiter("<[\\D|^-]*>");
					while (sc.hasNext()) {
						String str = sc.next().trim();
						if (str.matches("[\\d|\\.|-]+")) {
							values[i][9] = Double.parseDouble(str);
							i++;
						}
					}
				}

				if (line.matches("^.*<b>Pr.dko.. wiatru max\\.</b>.*$")) {
					int i = 0;
					String nextLine = br.readLine();
					Scanner sc = new Scanner(nextLine).useDelimiter("<[\\D|^-]*>");
					while (sc.hasNext()) {
						String str = sc.next().trim();
						if (str.matches("[\\d|\\.|-]+")) {
							values[i][10] = Double.parseDouble(str);
							i++;
						}
					}
				}

				if (line.matches("^.*<b>Kierunek wiatru</b>.*$")) {
					int i = 0;
					String nextLine = br.readLine();
					Scanner sc = new Scanner(nextLine).useDelimiter("<[\\D|^-]*>");
					while (sc.hasNext()) {
						String str = sc.next().trim();
						if (str.matches("[\\d|\\.|-]+")) {
							values[i][11] = Double.parseDouble(str);
							i++;
						}
					}
				}
			}
			br.close();
			for (int i = 0; i < values.length; i++) {
				Measurement measurement;
				if (times[i] < 1314907200000l) {
					if ((measurement = findMeasurement(Measurement.TIME,
							times[i])) != null) {
						updateMeasurement(measurement, values[i]);
					}
				}
			}

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * @brief Metoda pomocnicza tworzaca obiekt typu Measurement (pomiar).
	 * 
	 *        Metoda tworzy obiet pomiarowy, nadaje mu wartosc pola
	 *        id, parsuje wszystkie dane w linijce, ktore są pakowane
	 *        do tablicy, następnie przesyła je w takiej postaci do 
	 *        metody klasy Measurement - setValues(Double[]). 
	 *        
	 */

	private Measurement createMeasurement(String[] fields, int id) {
		Measurement measurement = new Measurement();

		measurement.setId(id);
		
		/* nowy kod */
		double[] values = new double[19];
		
		for (int i = 0; i < fields.length; i++) {
			values[i] = Double.parseDouble(fields[i]);
		}
		measurement.setValues(values);


		
//				Calendar calendar = Calendar.getInstance();
//
//		try {
//
//			// pole czasu jest podane w sekundach, stąd x1000
//			calendar.setTimeInMillis(Long.parseLong(fields[0]) * 1000);
//		
//			// wyciągam godzinę i minuty z tablicy pól
//			String[] time = fields[1].split("\\:", 2);
//			calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(time[0]));
//			calendar.set(Calendar.MINUTE, Integer.parseInt(time[1]));
//			measurement.setTime(calendar.getTimeInMillis());
//
//			// sprawdzam czy wszystkie dane są poprawne
//			for (int i = 2; i < fields.length; i++) {
//				if (fields[i].equals("-1")) {
//					// System.out.println(fields[i]);
//					throw new NumberFormatException();
//				}
//			}
//
//			measurement.setIntensity(Integer.parseInt(fields[2]));
//			measurement.setAvgSpeed(Integer.parseInt(fields[3]));
//
//		} catch (NumberFormatException nfe) {
//
//			// w przypadku braku danych, nowe wyznaczamy jako średnia
//			// arytmetyczna z dwóch poprzendich pomiarów
//			Measurement generatedMeasurement = computeMeasurement();
//
//			// System.out.println(measurement.getId()+"  "+generatedMeasurement.getIntensity()+"  "
//			// +generatedMeasurement.getAvgSpeed());
//			measurement.setIntensity(generatedMeasurement.getIntensity());
//			measurement.setAvgSpeed(generatedMeasurement.getAvgSpeed());
//
//		}

		return measurement;
	}

	/**
	 * @brief Metoda pomocnicza generujaca pomiar.
	 * 
	 *        Na podstawie poprzednich 2 pomiarow wyznaczone sa nowe dane.
	 *        Srednia arytmetyczna odpowiednich pol
	 */

	
	private Measurement computeMeasurement() {

		Measurement newMeasurement = new Measurement();

		if (list.size() > 1) {
			Measurement m1 = list.getLast();
			Measurement m2 = list.get(list.size() - 2);

			double tmpIntensity = (m1.getIntensity() + m2.getIntensity()) / 2;
			double tmpAvgSpeed = (m1.getAvgSpeed() + m2.getAvgSpeed()) / 2;

			newMeasurement.setIntensity(tmpIntensity);
			newMeasurement.setAvgSpeed(tmpAvgSpeed);
		} else {
			newMeasurement.setIntensity(0);
			newMeasurement.setAvgSpeed(0);
		}
		return newMeasurement;

	}
	
	/**
	 * @brief Metoda wyszukujaca pomiar.
	 * 
	 *        Metoda moze szukac danego pomiaru poprzez podanie
	 *        interesujacego nas pola oraz wartosci. Zostaly
	 *        zaimplementwane dwa kryteria przeszukiwania 
	 *        - po wartosci Id
	 *        - po wartosci czasu w ms
	 *    
	 */

	private Measurement findMeasurement(int field, long value) {
		Measurement measurement = null;
		switch (field) {
		case 0: {
			int i = 0;
			boolean notFound = true;
			while (i < list.size() && notFound) {
				if (list.get(i).getId() == value) {
					notFound = false;
					measurement = list.get(i);
				}
				i++;
			}
			break;
		}
		case 1: {
			int i = 0;
			boolean notFound = true;
			while (i < tmpList.size() && notFound) {
				if (tmpList.get(i).getTime() == value) {
					notFound = false;
					measurement = tmpList.get(i);
					tmpList.remove(i);
				}
				i++;
			}
			break;
		}
		}
		return measurement;
	}
	
	/**
	 * @brief Metoda przetwarzajaca i aktualizujaca pomiar
	 * 
	 *        Metodzie klasy Measurement przekazywana jest
	 *        tablica aktualnych wartosci.
	 *    
	 */

	private void updateMeasurement(Measurement measurement, double[] values) {
		measurement.addData(values);
	}
	
	/**
	 * @brief Metoda zwracajaca dane.
	 * 
	 *       Dane sa zwracane w postaci obiektu klasy
	 *       DataSource.
	 *    
	 */

	public DataSource getDataSource() {
		return dataSource;
	}
}
